﻿using System.Collections.Generic;
using System.Text;
using System;

namespace Ex03.GarageLogic
{
    public abstract class Vehicle
    {
        private const string k_PlateNumberKey = "Plate Number";
        private const string k_ManufacturerKey = "Manufacturer";
        private const string k_EnergyPercentageKey = "Energy Percentage";
        private const string k_OwnerNameKey = "Owner Name";
        private const string k_OwnerPhoneKey = "Owner Phone";
        private const float k_MaxPercentage = 100.0f;
        private const float k_MinPercentage = 0.0f;
        private const int k_ProperPlateLength = 5;
        private const int k_MinPhoneLength = 5;
        private const int k_MaxPhoneLength = 10;
                
        private List<Wheel> m_Wheels; 
        private string m_PlateNumber;        
        private string m_Manufacturer;
        private float m_EnergyPercentage;
        private string m_OwnerName;
        private string m_OwnerPhone;
        private eVehicleStatusInGarage m_CurrentStatusInGarage;
        
        protected Vehicle()
        {
            m_PlateNumber = string.Empty;
            RemainingEnergyPercentage = 0.0f.ToString();
            m_OwnerPhone = string.Empty;
            m_Manufacturer = string.Empty;
            m_OwnerName = string.Empty;
            m_Wheels = new List<Wheel>();
            m_CurrentStatusInGarage = eVehicleStatusInGarage.Fixing;
        }

        public static bool IsValidPlateNumber(string i_PlateNumber)
        {
            bool isValidPlateNumber = false;

            if (i_PlateNumber.Length == k_ProperPlateLength)
            {
                isValidPlateNumber = true;

                foreach (char digit in i_PlateNumber)
                {
                    isValidPlateNumber = isValidPlateNumber && char.IsDigit(digit);
                }

                if (!isValidPlateNumber)
                {
                    throw new System.ArgumentException(k_PlateNumberKey);
                }
            }

            return isValidPlateNumber;
        }

        public virtual void ParseFields(Dictionary<string, string> i_Form)
        { 
            string manufacturerStr;
            if (!i_Form.TryGetValue(k_ManufacturerKey, out manufacturerStr))
            {
                throw new KeyNotFoundException(Vehicle.k_ManufacturerKey);
            }

            string energyPercentageStr;
            if (!i_Form.TryGetValue(k_EnergyPercentageKey, out energyPercentageStr))
            {
                throw new KeyNotFoundException(k_EnergyPercentageKey);
            }

            string ownerNameStr;
            if (!i_Form.TryGetValue(k_OwnerNameKey, out ownerNameStr))
            {
                throw new KeyNotFoundException(k_OwnerNameKey);
            }

            string ownerPhoneStr;
            if (!i_Form.TryGetValue(Vehicle.k_OwnerPhoneKey, out ownerPhoneStr))
            {
                throw new KeyNotFoundException(k_OwnerPhoneKey);
            }

            NameOfManufacturer = manufacturerStr;
            OwnerName = ownerNameStr;
            OwnerPhone = ownerPhoneStr;
            RemainingEnergyPercentage = energyPercentageStr;
        }

        public virtual Dictionary<string, string> CreateForm()
        {
            Dictionary<string, string> form = new Dictionary<string, string>();
            form.Add(k_OwnerNameKey, null);
            form.Add(k_OwnerPhoneKey, null);
            form.Add(k_ManufacturerKey, null);
            form.Add(k_EnergyPercentageKey, null);
            
            return form;
        }
        
        public enum eVehicleStatusInGarage 
        {
            Fixing,
            Fixed,
            Paid
        }

        public eVehicleStatusInGarage CurrentStatusInGarage
        {
            get { return m_CurrentStatusInGarage; }
            set { m_CurrentStatusInGarage = value; }
        }

        public string NameOfManufacturer
        {
            get { return m_Manufacturer; }
            set { m_Manufacturer = value; }
        }

        public string PlateNumber
        {
            get { return m_PlateNumber; }
            set
            {
                bool isValidPlateNumber = IsValidPlateNumber(value);

                if (!isValidPlateNumber)
                {
                    throw new System.ArgumentException(k_PlateNumberKey);
                }
            
                m_PlateNumber = value;
            }
        }

        public string RemainingEnergyPercentage
        {
            get { return m_EnergyPercentage.ToString(); }
            set 
            {
                float floatVal = -1.0f;

                try
                {
                    floatVal = float.Parse(value);
                }
                catch (FormatException)
                {
                    throw new FormatException(k_EnergyPercentageKey);
                }

                bool isValidPercentage = false;
                isValidPercentage = (floatVal <= k_MaxPercentage) && (floatVal >= k_MinPercentage);

                if (!isValidPercentage)
                {
                    throw new ValueOutOfRangeException(k_EnergyPercentageKey, 0.0f, 100.0f);
                }
                
                m_EnergyPercentage = floatVal; 
            }            
        }

        public List<Wheel> Wheels
        {
            get { return m_Wheels; }
            protected set { m_Wheels = value; }
        }

        protected List<Wheel> ParseWheels(Dictionary<string, string> i_Form, int i_NumberOfWheels, float i_MaximumAirPressure)
        {
            List<Wheel> wheels = new List<Wheel>(i_NumberOfWheels);

            for (int i = 1; i <= i_NumberOfWheels; ++i)
            {
                wheels.Add(Wheel.makeSingleWheel(i_Form, i, i_MaximumAirPressure));
            }

            return wheels;
        }

        public string OwnerName
        {
            get { return m_OwnerName; }
            set { m_OwnerName = value; }
        }

        public string OwnerPhone
        {
            get { return m_OwnerPhone; }
            set 
            {
                bool isValidPhoneNumber = false;

                if (value.Length >= k_MinPhoneLength && value.Length <= k_MaxPhoneLength)
                {
                    isValidPhoneNumber = true;

                    foreach (char digit in value)
                    {
                        isValidPhoneNumber = isValidPhoneNumber && char.IsDigit(digit);
                    }
                }

                if (!isValidPhoneNumber)
                {
                    throw new System.FormatException(k_OwnerPhoneKey);
                }
    
                m_OwnerPhone = value; 
            }            
        }

        public override string ToString()
        {
            StringBuilder baseStringBuilder = new StringBuilder();
            baseStringBuilder.Append("#");
            baseStringBuilder.AppendLine(m_PlateNumber);
            baseStringBuilder.Append("Status in garage: ");
            baseStringBuilder.AppendLine(CurrentStatusInGarage.ToString());
            baseStringBuilder.Append("Owner Name: ");
            baseStringBuilder.AppendLine(OwnerName.ToString());
            baseStringBuilder.Append("Owner Phone: ");
            baseStringBuilder.AppendLine(OwnerPhone.ToString());
            baseStringBuilder.Append("Name Of Manufacturer: ");
            baseStringBuilder.AppendLine(NameOfManufacturer.ToString());
            baseStringBuilder.Append("Remaining Energy Percentage: ");
            baseStringBuilder.AppendLine(RemainingEnergyPercentage.ToString());

            baseStringBuilder.AppendLine("Wheels:");
            int i = 1;
            foreach (Wheel wheel in Wheels)
            {
                baseStringBuilder.Append("#" + i.ToString() + " ");
                baseStringBuilder.AppendLine(wheel.ToString());
                i++;
            }

            return baseStringBuilder.ToString();
        }

        public class Wheel
        {
            private const string k_WheelManufacturerKey = "Manufacturer";
            private const string k_CurrentAirPressureKey = "Current Air Pressure";
            private const string k_CurrentWheelPrefix = "Wheel {0} ";

            private float m_MaximumAirPressure;
            private string m_NameOfManufacturer;
            private float m_CurrentAirPressure;

            public Wheel()
            {
                m_NameOfManufacturer = null;
                MaximumAirPressure = 0.0f;
                m_CurrentAirPressure = 0.0f;
            }

            internal static void CreateForm(ref Dictionary<string, string> i_Form, int i_NumberOfWheels)
            {
                for (int i = 1; i < i_NumberOfWheels + 1; i++)
                {
                    string currentWheelKeyStr = string.Format(k_CurrentWheelPrefix, i);

                    i_Form.Add(currentWheelKeyStr + k_WheelManufacturerKey, null);
                    i_Form.Add(currentWheelKeyStr + k_CurrentAirPressureKey, null);
                }
            }

            public void Inflate(float i_AirPressureToAdd)
            {
                if (i_AirPressureToAdd + m_CurrentAirPressure <= m_MaximumAirPressure)
                {
                    m_CurrentAirPressure += i_AirPressureToAdd;
                }
                else
                {
                    throw new ValueOutOfRangeException("Wheel Inflate", 0.0f, m_MaximumAirPressure - m_CurrentAirPressure);
                }
            }

            public override string ToString()
            {
                StringBuilder stringBuilder = new StringBuilder();

                stringBuilder.Append("Manufacturer: ");
                stringBuilder.Append(NameOfManufacturer.ToString());
                stringBuilder.Append(" ");
                stringBuilder.Append("Current Air Pressure: ");
                stringBuilder.Append(CurrentAirPressure.ToString());
                stringBuilder.Append(" ");
                stringBuilder.Append("Maximum Air Pressure: ");
                stringBuilder.Append(MaximumAirPressure.ToString());

                return stringBuilder.ToString();
            }

            public string CurrentAirPressure
            {
                get { return m_CurrentAirPressure.ToString(); }
                set 
                {
                    float floatAirPressureValue;
                    try
                    {
                        floatAirPressureValue = float.Parse(value);
                    }
                    catch (FormatException)
                    {
                        throw new FormatException(k_CurrentAirPressureKey);
                    }

                    if (floatAirPressureValue > MaximumAirPressure || floatAirPressureValue < 0.0f)
                    {
                        throw new ValueOutOfRangeException(k_CurrentAirPressureKey, 0.0f, MaximumAirPressure);
                    }

                    m_CurrentAirPressure = floatAirPressureValue; 
                }
            }

            public float MaximumAirPressure
            {
                get { return m_MaximumAirPressure; }
                internal set { m_MaximumAirPressure = value; }
            }

            public string NameOfManufacturer
            {
                get { return m_NameOfManufacturer; }
                set { m_NameOfManufacturer = value; }
            }

            internal static Wheel makeSingleWheel(Dictionary<string, string> i_Form, int i_CurrentWheelIndex, float i_MaxAirPressure)
            {
                string manufacturerWheelKey;
                string currentAirPressureWheelKey;

                // prepare keys for extraction from dictionary
                currentAirPressureWheelKey = manufacturerWheelKey = string.Format(k_CurrentWheelPrefix, i_CurrentWheelIndex);
                manufacturerWheelKey += k_WheelManufacturerKey;
                currentAirPressureWheelKey += k_CurrentAirPressureKey;

                // check existance
                string manufacturerWheelStr;
                if (!i_Form.TryGetValue(manufacturerWheelKey, out manufacturerWheelStr))
                {
                    throw new KeyNotFoundException(manufacturerWheelKey);
                }
                
                string airCurrentPressureWheelStr;
                if (!i_Form.TryGetValue(currentAirPressureWheelKey, out airCurrentPressureWheelStr))
                {
                    throw new KeyNotFoundException(currentAirPressureWheelKey);
                }

                Wheel wheelToReturn = new Wheel();
                wheelToReturn.MaximumAirPressure = i_MaxAirPressure;
                wheelToReturn.NameOfManufacturer = manufacturerWheelStr;

                try
                {
                    wheelToReturn.CurrentAirPressure = airCurrentPressureWheelStr;
                }
                catch (ValueOutOfRangeException valueException)
                {
                    throw new ValueOutOfRangeException(currentAirPressureWheelKey, valueException.MinValue, valueException.MaxValue);
                }
                catch (FormatException)
                {
                    throw new FormatException(currentAirPressureWheelKey);
                }

                return wheelToReturn;
            }
         }

        public void InflateAllWheels()
        {
            foreach (Wheel wheel in m_Wheels)
            {
                wheel.Inflate(wheel.MaximumAirPressure - float.Parse(wheel.CurrentAirPressure));
            }
        }
    }
}